﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MineSweeperChallenge
{
    public class Game
    {
        public class Result
        {
            public bool Win { get; private set; }
            public double TimeTakenMilliseconds { get; private set; }
            public bool HadException { get; set; }

            public Result(bool pWin, double pTimeTakenMilliseconds)
            {
                Win = pWin;
                TimeTakenMilliseconds = pTimeTakenMilliseconds;
            }
        }

        private ISweeper mSweeper;

        private int[][] mMinefieldSecret;
        private int mNumberOfMines;

        internal int[][] mPlayingMineField;
        private DateTime mStartTime;

        public Game(ISweeper pSweeper, int pWidth, int pHeight, int pNumberOfMines)
        {
            if (pSweeper == null) throw new ArgumentNullException("A sweeper implementation must be provided");
            if (pNumberOfMines >= pWidth * pHeight) throw new ArgumentOutOfRangeException("Number of mines must be less than the total amount of fields");
            if (pWidth <= 0) throw new ArgumentOutOfRangeException("Width must be more than 0");
            if (pHeight <= 0) throw new ArgumentOutOfRangeException("Height must be more than 0");
            if (pNumberOfMines <= 0) throw new ArgumentOutOfRangeException("Number of mines must be more than 0");

            mSweeper = pSweeper;
            mNumberOfMines = pNumberOfMines;

            // build empty minefield
            FillMinefield(ref mMinefieldSecret, pWidth, pHeight, 0);

            // place mines on the minefield; could be made more effecient for minefields with a lot of mines
            Random lRandom = new Random();
            for (int i = 0; i < mNumberOfMines; i++)
            {
                // find a field for the mine
                int lX, lY;
                do
                {
                    lX = lRandom.Next(0, pWidth);
                    lY = lRandom.Next(0, pHeight);
                } while (mMinefieldSecret[lX][lY] < 0);
                mMinefieldSecret[lX][lY] = -1;
                
                // update surrounding fields
                bool lIsBoundLeft = lX <= 0;
                bool lIsBoundRight = lX >= pWidth - 1;
                bool lIsBoundTop = lY <= 0;
                bool lIsBoundBottom = lY >= pHeight - 1;
                if (! lIsBoundLeft && ! lIsBoundTop) AddOne(ref mMinefieldSecret[lX - 1][lY - 1]);
                if (! lIsBoundTop) AddOne(ref mMinefieldSecret[lX][lY - 1]);
                if (! lIsBoundRight && ! lIsBoundTop) AddOne(ref mMinefieldSecret[lX + 1][lY - 1]);
                if (! lIsBoundRight) AddOne(ref mMinefieldSecret[lX + 1][lY]);
                if (! lIsBoundRight && ! lIsBoundBottom) AddOne(ref mMinefieldSecret[lX + 1][lY + 1]);
                if (! lIsBoundBottom) AddOne(ref mMinefieldSecret[lX][lY + 1]);
                if (! lIsBoundLeft && ! lIsBoundBottom) AddOne(ref mMinefieldSecret[lX - 1][lY + 1]);
                if (! lIsBoundLeft) AddOne(ref mMinefieldSecret[lX - 1][lY]);
            }
        }

        private void FillMinefield(ref int[][] pMineField, int pWidth, int pHeight, int pValue)
        {
            pMineField = new int[pWidth][];
            for (int i = 0; i < pWidth; i++)
            {
                pMineField[i] = new int[pHeight];
                for (int j = 0; j < pHeight; j++)
                {
                    pMineField[i][j] = pValue;
                }
            }
        }

        public void Initialize()
        {
            mPlayingMineField = null;
            FillMinefield(ref mPlayingMineField, mMinefieldSecret.Length, mMinefieldSecret[0].Length, -2);
            mStartTime = DateTime.Now;
        }

        public Result Play()
        {
            int lLoopSafe = mMinefieldSecret.Length * mMinefieldSecret[0].Length;
            do
            {
                Result lResult = PlayStep();
                if (lResult != null) return lResult;
                lLoopSafe--;
                if (lLoopSafe <= 0) return new Result(false, (DateTime.Now - mStartTime).TotalMilliseconds);
            } while(mPlayingMineField.SelectMany(pArray => pArray).Count(pValue => pValue == -2) > mNumberOfMines);
            return new Result(true, -1);
        }

        public Result PlayStep()
        {
            int[] lCoordinate;
            try
            {
                lCoordinate = mSweeper.Sweep(mPlayingMineField, mNumberOfMines);
            }
            catch (Exception)
            {
                return new Result(false, (DateTime.Now - mStartTime).TotalMilliseconds) { HadException = true };
            }
            mPlayingMineField[lCoordinate[0]][lCoordinate[1]] = mMinefieldSecret[lCoordinate[0]][lCoordinate[1]];
            if (mPlayingMineField[lCoordinate[0]][lCoordinate[1]] == -1) return new Result(false, (DateTime.Now - mStartTime).TotalMilliseconds);
            return null;
        }

        private static void AddOne(ref int pField)
        {
            if (pField >= 0) pField++;
        }
    }
}
